package erfgame.core.world;

import java.awt.Point;
import java.io.Serializable;

import erfgame.core.event.EventQueue;

public class World implements Serializable {
	
	public static final int getShift( int i ) {
		int shift = -1;
		while( i > 0 ) {
			i >>= 1;
			shift++;
		}
		return shift;
	}
	
	public static final int getMask( int i ) {
		return i-1;
	}
	
	private static final long serialVersionUID = 0;
	
	private int blockWidth;
	private int blockWidthShift;
	private int blockWidthMask;
	private int blockHeight;
	private int blockHeightShift;
	private int blockHeightMask;
	private int blockDepth;

	private Point blockMidPointTimes2;	
	private Point worldBlockMidPointTimes2;

	private int blocksX;
	private int blocksY;
	private int blocksZ;
	
	private int worldId;
	private String worldName;
	
	private int gridDiagonal;
	private int gridDiagonalMask;
	private int gridDiagonalShift;
	
	private int worldDepthShift;
	private int worldDepth;
	
	private int xGridTilesPerBlock;
	private int xGridTilesPerBlockShift;
	private int xGridTilesPerBlockMask;
	
	private int yGridTilesPerBlock;
	private int yGridTilesPerBlockShift;
	private int yGridTilesPerBlockMask;
	
	private long worldTime;
	private long nextEntityId;
	
	private int maxShadowDepth;
	
	// TODO : probably shouldn't be transient
	private transient EventQueue eventQueue;
	
	public World() {
		this.blockMidPointTimes2 = new Point();
		this.worldBlockMidPointTimes2 = new Point();
	}
	
	public void setEventQueue( EventQueue eventQueue ) {
		this.eventQueue = eventQueue;
	}
	
	public EventQueue getEventQueue() {
		return this.eventQueue;
	}
	
	public int getWorldId() {
		return worldId;
	}

	public void setWorldId(int worldId) {
		this.worldId = worldId;
	}

	public String getWorldName() {
		return worldName;
	}

	public void setWorldName(String worldName) {
		this.worldName = worldName;
	}

	public int getWorldWidth() {
		return this.blocksX * this.blockWidth;
	}
	
	public int getWorldHeight() {
		return this.blocksY * this.blockHeight;
	}
	
	public int getWorldDepth() {
		return this.worldDepth;
	}

	public int getBlockWidth() {
		return blockWidth;
	}

	public void setBlockWidth(int blockWidth) {
		this.blockWidth = blockWidth;
		this.blockWidthShift = getShift( blockWidth );
		this.blockWidthMask = getMask( blockWidth );
		this.blockMidPointTimes2.x = blockWidth - 1;
		calculateGridTiles();
	}

	public int getBlockHeight() {
		return blockHeight;
	}

	public void setBlockHeight(int blockHeight) {
		this.blockHeight = blockHeight;
		this.blockHeightShift = getShift( blockHeight );
		this.blockHeightMask = getMask( blockHeight );
		this.blockMidPointTimes2.y = blockHeight - 1;
		calculateGridTiles();
	}

	public int getBlockDepth() {
		return blockDepth;
	}

	public void setBlockDepth(int blockDepth) {
		this.blockDepth = blockDepth;
		calculateWorldDepth();
		calculateWorldDepthShift();
	}

	public int getBlocksX() {
		return blocksX;
	}

	public void setBlocksX(int blocksX) {
		this.blocksX = blocksX;
		this.worldBlockMidPointTimes2.x = blocksX - 1;
	}

	public int getBlocksY() {
		return blocksY;
	}

	public void setBlocksY(int blocksY) {
		this.blocksY = blocksY;
		this.worldBlockMidPointTimes2.y = blocksY - 1;
	}

	public int getBlocksZ() {
		return blocksZ;
	}

	public void setBlocksZ(int blocksZ) {
		this.blocksZ = blocksZ;
		calculateWorldDepth();
		calculateWorldDepthShift();
	}
	
	public int getGridDiagonal() {
		return this.gridDiagonal;
	}
	
	public void setGridDiagonal( int gridDiagonal ) {
		this.gridDiagonal = gridDiagonal;
		calculateGridDiagonalMask();
		calculateGridTiles();
	}
	
	public int getXGridTilesPerBlock() {
		return this.xGridTilesPerBlock;
	}
	
	public int getYGridTilesPerBlock() {
		return this.yGridTilesPerBlock;
	}
	
	public int getXPathTilesPerBlock() {
		return getXGridTilesPerBlock() << 1;
	}
	
	public int getXPathTilesPerBlockShift() {
		return getxGridTilesPerBlockShift() + 1;
	}
	
	public int getXPathTilesPerBlockMask() {
		return (getxGridTilesPerBlockMask() << 1) | 0x01;
	}
	
	public int getYPathTilesPerBlock() {
		return getYGridTilesPerBlock();
	}
	
	public int getYPathTilesPerBlockShift() {
		return getyGridTilesPerBlockShift();
	}
	
	public int getYPathTilesPerBlockMask() {
		return getyGridTilesPerBlockMask();
	}
	
	public int getWorldDepthShift() {
		return this.worldDepthShift;
	}
	
	public int getGridDiagonalMask() {
		return this.gridDiagonalMask;
	}
	
	public int getGridDiagonalShift() {
		return this.gridDiagonalShift;
	}
	
	public int getBlockWidthShift() {
		return blockWidthShift;
	}

	public int getBlockWidthMask() {
		return blockWidthMask;
	}

	public int getBlockHeightShift() {
		return blockHeightShift;
	}

	public int getBlockHeightMask() {
		return blockHeightMask;
	}
	
	public int getxGridTilesPerBlockShift() {
		return xGridTilesPerBlockShift;
	}

	public int getxGridTilesPerBlockMask() {
		return xGridTilesPerBlockMask;
	}

	public int getyGridTilesPerBlockShift() {
		return yGridTilesPerBlockShift;
	}

	public int getyGridTilesPerBlockMask() {
		return yGridTilesPerBlockMask;
	}
	
	public Point getBlockMidPointTimes2() {
		return this.blockMidPointTimes2;
	}
	
	public Point getWorldBlockMidPointTimes2() {
		return this.worldBlockMidPointTimes2;
	}
	
	public long getWorldTime() {
		return this.worldTime;
	}
	
	public void setWorldTime( long worldTime ) {
		this.worldTime = worldTime;
	}
	
	public long getNextEntityId() {
		return this.nextEntityId++;
	}

	private void calculateWorldDepthShift() {
		int worldDepth = this.getWorldDepth();
		int shift = getShift( worldDepth );
		this.worldDepthShift = shift;
	}
	
	private void calculateGridDiagonalMask() {
		this.gridDiagonalMask = getMask( this.gridDiagonal );
		this.gridDiagonalShift = getShift( this.gridDiagonal );
	}
	
	private void calculateWorldDepth() {
		this.worldDepth = this.blockDepth * this.blocksZ;
	}
	
	private void calculateGridTiles() {
		if( gridDiagonal != 0 ) {
			this.xGridTilesPerBlock = this.blockWidth / this.gridDiagonal;
			this.xGridTilesPerBlockMask = getMask( this.xGridTilesPerBlock );
			this.xGridTilesPerBlockShift = getShift( this.xGridTilesPerBlock );

			this.yGridTilesPerBlock = (this.blockHeight * 2) / this.gridDiagonal;
			this.yGridTilesPerBlockMask = getMask( this.yGridTilesPerBlock );
			this.yGridTilesPerBlockShift = getShift( this.yGridTilesPerBlock );
		}
	}
	
	public Point getGridCoordinates( int tx, int ty ) {
		return getGridCoordinates( tx, ty, new Point() );
	}
	
	public Point getGridCoordinates( int tx, int ty, Point p ) {
		int gridDiagonal = getGridDiagonal();
		int nx = tx / gridDiagonal;
		int ny = ty / gridDiagonal;
		int mx = tx % gridDiagonal;
		int my = ty % gridDiagonal;
		boolean a = gridDiagonal - mx < my;
		boolean b = mx > my;
		int gx;
		int gy;
		if( a && b ) {
			gx = nx + 1;
		} else {
			gx = nx;
		}
		if( a ^ !b ) {
			gy = ny * 2 + 1;
		} else if( a ) {
			gy = ny * 2 + 2;
		} else {
			gy = ny * 2;
		}
		p.x = gx;
		p.y = gy;
		return p;
	}
	
	public byte getGridId( int gx, int gy) {
		return (byte)(gy * (getXGridTilesPerBlock()+1) + gx);
	}
	

	public int getMaxShadowDepth() {
		return maxShadowDepth;
	}

	public void setMaxShadowDepth(int maxShadowDepth) {
		this.maxShadowDepth = maxShadowDepth;
	}

	@Override
	public boolean equals(Object o) {
		if( o != null && o.getClass().equals( this.getClass() ) ) {
			return ((World)o).getWorldId() == this.getWorldId();
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return this.worldId;
	}
}
